package module.course.impl;

import common.model.Entity.courseEntity.Course;
import common.model.Entity.courseEntity.CourseLocation;
import common.model.Entity.courseEntity.CourseSchedule;
import common.util.DBUtil;
import dao.CourseDao.CourseDao;
import dao.CourseDao.CourseLocationDao;
import dao.CourseDao.CourseScheduleDao;
import dao.impl.CourseDaoImpl.CourseDaoImpl;
import dao.impl.CourseDaoImpl.CourseLocationDaoImpl;
import dao.impl.CourseDaoImpl.CourseScheduleDaoImpl;
import network.protocol.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * 管理员排课功能的实现类。
 * <p>
 * 提供课程排课、获取已排课信息、时间段检查和课程安排等功能。
 * 使用数据库事务保证排课过程的一致性。
 */
public class ScheduleCourseImpl {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleCourseImpl.class);

    private final CourseDao courseDao;
    private final CourseScheduleDao courseScheduleDao;
    private final CourseLocationDao courseLocationDao;
    private final static ScheduleCourseImpl INSTANCE = new ScheduleCourseImpl();

    /**
     * 获取 ScheduleCourseImpl 单例实例。
     *
     * @return ScheduleCourseImpl 实例
     */
    public static ScheduleCourseImpl getInstance() {
        return INSTANCE;
    }

    /**
     * 构造函数，初始化 DAO 对象。
     */
    public ScheduleCourseImpl() {
        this.courseDao = new CourseDaoImpl();
        this.courseScheduleDao = new CourseScheduleDaoImpl();
        this.courseLocationDao = new CourseLocationDaoImpl();
    }

    /**
     * 执行课程排课操作。
     * <p>
     * 排课逻辑：
     * 1. 获取所有需要排课的课程
     * 2. 获取所有教室
     * 3. 获取已排课信息
     * 4. 按每周课时量从大到小排序
     * 5. 尝试为每门课程安排时间
     *
     * @return 排课结果 Response<Boolean>
     */
    public Response<Boolean> scheduleCourse() {
        Connection conn = null;
        try {
            logger.info("开始执行管理员排课功能");
            conn = DBUtil.getConnection();
            conn.setAutoCommit(false);

            // 获取需要排课的课程
            List<Course> coursesToSchedule = courseDao.findCoursesToSchedule();
            if (coursesToSchedule.isEmpty()) {
                logger.info("没有需要排课的课程");
                return Response.success(true);
            }

            // 获取所有教室
            List<CourseLocation> locations = courseLocationDao.findAll();
            if (locations.isEmpty()) {
                logger.error("没有可用的教室");
                return Response.error("没有可用的教室，无法排课");
            }

            // 获取已排课信息
            Map<String, List<CourseScheduleWithWeeks>> scheduledCourses = getScheduledCoursesWithWeeks(conn);

            // 按每周课时量排序
            coursesToSchedule.sort((c1, c2) -> Integer.compare(calculatePeriodsPerWeek(c2), calculatePeriodsPerWeek(c1)));

            // 尝试排课
            int successfullyScheduled = 0;
            for (Course course : coursesToSchedule) {
                if (scheduleSingleCourse(conn, course, locations, scheduledCourses)) {
                    successfullyScheduled++;
                }
            }

            conn.commit();
            logger.info("排课完成: 成功安排 {} 门课程，失败 {} 门课程",
                    successfullyScheduled, coursesToSchedule.size() - successfullyScheduled);

            return Response.success(true);
        } catch (Exception e) {
            logger.error("排课过程中发生错误", e);
            if (conn != null) {
                try {
                    conn.rollback();
                    logger.info("已回滚事务");
                } catch (SQLException ex) {
                    logger.error("回滚事务失败", ex);
                }
            }
            return Response.error("排课失败: " + e.getMessage());
        } finally {
            if (conn != null) {
                try {
                    conn.setAutoCommit(true);
                    conn.close();
                } catch (SQLException e) {
                    logger.error("关闭数据库连接失败", e);
                }
            }
        }
    }

    /**
     * 计算每周课时量。
     *
     * @param course 待计算课程
     * @return 每周课时数
     */
    private int calculatePeriodsPerWeek(Course course) {
        int weeks = course.getWeekEnd() - course.getWeekStart() + 1;
        return course.getClassPeriod() / weeks;
    }

    /**
     * 获取已排课信息映射表，包括周次。
     *
     * @param conn 数据库连接
     * @return 已排课映射表
     * @throws SQLException 数据库操作异常
     */
    private Map<String, List<CourseScheduleWithWeeks>> getScheduledCoursesWithWeeks(Connection conn) throws SQLException {
        Map<String, List<CourseScheduleWithWeeks>> scheduledCourses = new HashMap<>();
        String sql = "SELECT cs.*, c.week_start, c.week_end " +
                "FROM t_course_schedule cs " +
                "JOIN t_course c ON cs.course_id = c.id " +
                "WHERE cs.is_deleted = 0";

        try (PreparedStatement pstmt = conn.prepareStatement(sql);
             ResultSet rs = pstmt.executeQuery()) {
            while (rs.next()) {
                CourseSchedule schedule = new CourseSchedule();
                schedule.setId(rs.getLong("id"));
                schedule.setCourseId(rs.getLong("course_id"));
                schedule.setDayOfWeek(rs.getInt("day_of_week"));
                schedule.setStartTime(rs.getInt("start_time"));
                schedule.setEndTime(rs.getInt("end_time"));
                schedule.setLocation(rs.getString("location"));

                int weekStart = rs.getInt("week_start");
                int weekEnd = rs.getInt("week_end");

                CourseScheduleWithWeeks scheduleWithWeeks = new CourseScheduleWithWeeks(schedule, weekStart, weekEnd);

                String key = schedule.getLocation() + "-" + schedule.getDayOfWeek();
                scheduledCourses.computeIfAbsent(key, k -> new ArrayList<>()).add(scheduleWithWeeks);
            }
        }
        return scheduledCourses;
    }

    /**
     * 为单门课程安排时间。
     *
     * @param conn             数据库连接
     * @param course           待排课程
     * @param locations        教室列表
     * @param scheduledCourses 已排课程映射
     * @return 是否成功安排
     */
    private boolean scheduleSingleCourse(Connection conn, Course course,
                                         List<CourseLocation> locations,
                                         Map<String, List<CourseScheduleWithWeeks>> scheduledCourses) {
        int periodsPerWeek = calculatePeriodsPerWeek(course);

        for (CourseLocation location : locations) {
            String locationStr = location.toString();

            for (int dayOfWeek = 1; dayOfWeek <= 5; dayOfWeek++) {
                for (TimeSlot timeSlot : TimeSlot.values()) {
                    if (timeSlot.end - timeSlot.start + 1 < periodsPerWeek) {
                        continue;
                    }

                    if (canScheduleInTimeSlot(locationStr, dayOfWeek, timeSlot,
                            periodsPerWeek, course, scheduledCourses)) {

                        if (insertCourseSchedule(conn, course, locationStr, dayOfWeek,
                                timeSlot.start, timeSlot.start + periodsPerWeek - 1)) {

                            CourseSchedule newSchedule = new CourseSchedule();
                            newSchedule.setCourseId(course.getId());
                            newSchedule.setDayOfWeek(dayOfWeek);
                            newSchedule.setStartTime(timeSlot.start);
                            newSchedule.setEndTime(timeSlot.start + periodsPerWeek - 1);
                            newSchedule.setLocation(locationStr);

                            CourseScheduleWithWeeks newScheduleWithWeeks = new CourseScheduleWithWeeks(
                                    newSchedule, course.getWeekStart(), course.getWeekEnd());

                            String key = locationStr + "-" + dayOfWeek;
                            scheduledCourses.computeIfAbsent(key, k -> new ArrayList<>()).add(newScheduleWithWeeks);

                            return true;
                        }
                    }
                }
            }
        }

        return false;
    }

    /**
     * 检查时间段是否能安排课程。
     *
     * @param location        教室
     * @param dayOfWeek       周几
     * @param timeSlot        时间段
     * @param periodsNeeded   需要的节数
     * @param course          待排课程
     * @param scheduledCourses 已排课程映射
     * @return 是否可排
     */
    private boolean canScheduleInTimeSlot(String location, int dayOfWeek, TimeSlot timeSlot,
                                          int periodsNeeded, Course course,
                                          Map<String, List<CourseScheduleWithWeeks>> scheduledCourses) {
        String key = location + "-" + dayOfWeek;
        List<CourseScheduleWithWeeks> schedules = scheduledCourses.getOrDefault(key, new ArrayList<>());

        boolean[] timeSlots = new boolean[timeSlot.end - timeSlot.start + 1];
        Arrays.fill(timeSlots, true);

        for (CourseScheduleWithWeeks scheduleWithWeeks : schedules) {
            if (isWeekOverlap(course.getWeekStart(), course.getWeekEnd(),
                    scheduleWithWeeks.weekStart, scheduleWithWeeks.weekEnd)) {
                CourseSchedule schedule = scheduleWithWeeks.schedule;

                for (int i = schedule.getStartTime(); i <= schedule.getEndTime(); i++) {
                    if (i >= timeSlot.start && i <= timeSlot.end) {
                        timeSlots[i - timeSlot.start] = false;
                    }
                }
            }
        }

        return findConsecutiveSlots(timeSlots, periodsNeeded);
    }

    /**
     * 检查周次是否重叠。
     *
     * @param weekStart1 周起始1
     * @param weekEnd1   周结束1
     * @param weekStart2 周起始2
     * @param weekEnd2   周结束2
     * @return 是否重叠
     */
    private boolean isWeekOverlap(int weekStart1, int weekEnd1, int weekStart2, int weekEnd2) {
        return !(weekEnd1 < weekStart2 || weekStart1 > weekEnd2);
    }

    /**
     * 查找连续空节。
     *
     * @param slots  空节数组
     * @param needed 需要连续节数
     * @return 是否找到
     */
    private boolean findConsecutiveSlots(boolean[] slots, int needed) {
        int count = 0;
        for (boolean slot : slots) {
            if (slot) {
                count++;
                if (count >= needed) return true;
            } else {
                count = 0;
            }
        }
        return false;
    }

    /**
     * 插入课程安排到数据库。
     *
     * @param conn      数据库连接
     * @param course    待排课程
     * @param location  教室
     * @param dayOfWeek 周几
     * @param startTime 起始节次
     * @param endTime   结束节次
     * @return 是否成功
     */
    private boolean insertCourseSchedule(Connection conn, Course course, String location,
                                         int dayOfWeek, int startTime, int endTime) {
        CourseSchedule schedule = new CourseSchedule();
        schedule.setCourseId(course.getId());
        schedule.setDayOfWeek(dayOfWeek);
        schedule.setStartTime(startTime);
        schedule.setEndTime(endTime);
        schedule.setLocation(location);

        boolean result = courseScheduleDao.save(schedule);

        course.setStatus(1);
        boolean result2 = courseDao.update(course);
        return result && result2;
    }

    /**
     * 时间段枚举类型。
     */
    private enum TimeSlot {
        MORNING(1, 5),
        AFTERNOON(6, 10),
        EVENING(11, 12);

        public final int start;
        public final int end;

        TimeSlot(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        public String toString() {
            return name() + "(" + start + "-" + end + ")";
        }
    }

    /**
     * 获取时间段名称。
     *
     * @param timeSlot 时间段
     * @return 名称
     */
    private String getTimeSlotName(TimeSlot timeSlot) {
        switch (timeSlot) {
            case MORNING: return "上午";
            case AFTERNOON: return "下午";
            case EVENING: return "晚上";
            default: return "";
        }
    }

    /**
     * 包含排课信息和周次的内部类。
     */
    private static class CourseScheduleWithWeeks {
        public final CourseSchedule schedule;
        public final int weekStart;
        public final int weekEnd;

        public CourseScheduleWithWeeks(CourseSchedule schedule, int weekStart, int weekEnd) {
            this.schedule = schedule;
            this.weekStart = weekStart;
            this.weekEnd = weekEnd;
        }
    }

    /**
     * 系统测试主方法。
     *
     * @param args 参数
     */
    public static void main(String[] args) {
        logger.info("开始排课系统测试");
        createTestData();
        Response<Boolean> response = ScheduleCourseImpl.getInstance().scheduleCourse();

        if (response.isSuccess()) {
            logger.info("排课测试成功: {}", response.getMsg());
        } else {
            logger.error("排课测试失败: {}", response.getMsg());
        }

        logger.info("排课系统测试完成");
    }

    /**
     * 创建测试数据。
     */
    private static void createTestData() {
        logger.info("创建测试数据...");
        try {
            logger.debug("清空现有测试数据");
            logger.debug("创建测试教室");
            logger.debug("创建测试课程");

            Course testCourse = new Course();
            testCourse.setCourseName("测试课程");
            testCourse.setWeekStart(1);
            testCourse.setWeekEnd(16);
            testCourse.setClassPeriod(32);

            logger.info("创建了测试课程: {}", testCourse.getCourseName());
        } catch (Exception e) {
            logger.error("创建测试数据失败", e);
        }
    }
}
